﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.SceneManagement;

public class AssetsLoadManager : MonoSingleton<AssetsLoadManager>
{
    private AssetBundleManifest manifest = null;

    private Dictionary<string, AssetBundle> loadedBundles = new Dictionary<string, UnityEngine.AssetBundle>();
    private Dictionary<string, AssetBundle> permanentBundles = new Dictionary<string, AssetBundle>();
    private HashSet<string> targets = new HashSet<string>();
    private void Awake()
    {
        // manifest
#if UNITY_EDITOR
        if (EditorAssetsLoadManager.SimulatorAssetsLoad) {
            loadManifest();
        }
#else
        loadManifest();
#endif
    }

    private void loadManifest()
    {
        AssetBundle manifestBundle = AssetBundle.LoadFromFile(PathDefine.PersistentBundlePath + "/" + PathDefine.AssetBundleName);
        if (null != manifestBundle) {
            manifest = (AssetBundleManifest)manifestBundle.LoadAsset("AssetBundleManifest");
            manifestBundle.Unload(false);
        }
    }

    public void Clear()
    {
        foreach(var pair in loadedBundles) {
            pair.Value.Unload(true);
        }
        loadedBundles.Clear();
        GC.Collect();
        Resources.UnloadUnusedAssets();
    }

    private void addBundleToCache(string bundleName, AssetBundle bundle)
    {
        AssetBundle permanentBundle;
        if (permanentBundles.TryGetValue(bundleName, out permanentBundle)) {
            permanentBundles[bundleName] = bundle;
        } else {
            loadedBundles.Add(bundleName, bundle);
        }
    }

    private AssetBundle getBundleFromCache(string bundleName)
    {
        AssetBundle permanentBundle;
        bool inPermanent = permanentBundles.TryGetValue(bundleName, out permanentBundle);
        AssetBundle loadBundle;
        bool inLoaded = loadedBundles.TryGetValue(bundleName, out loadBundle);
        if (inPermanent && inLoaded) {
            loadedBundles.Remove(bundleName);
            permanentBundles[bundleName] = loadBundle;
        }
        return null == permanentBundle ? loadBundle : permanentBundle;
    }

    /// <summary>
    /// 加载指定名字的AssetBundle
    /// </summary>
    /// <param name="bundleName">AssetBundle名字</param>
    /// <returns>返回AssetBundle</returns>
    public AssetBundle LoadAssetBundle(string bundleName)
    {
        targets.Clear();
        List<string> needBundles = new List<string>();
        CheckAssetBundlelDependencies(bundleName, needBundles);

        for (int i = 0; i < needBundles.Count; i++) {
            string name = needBundles[i];
            AssetBundle ab = getBundleFromCache(name);

            if (null == ab) {
                string filepath = string.Format("{0}/{1}", PathDefine.PersistentBundlePath, name);
                ab = AssetBundle.LoadFromFile(filepath);
                if (null != ab) {
                    addBundleToCache(name, ab);
                } else {
                    Debug.LogError("None AssetBundle at Path: " + filepath);
                }
            }
        }
     
        return getBundleFromCache(bundleName);
    }

    /// <summary>
    /// 从bundleName中加载assetName资源对象T
    /// </summary>
    /// <typeparam name="T">资源类型</typeparam>
    /// <param name="bundleName">AssetBundle名字</param>
    /// <param name="assetName">资源名字，不带后缀</param>
    /// <returns>加载的资源对象</returns>
    public T LoadAsset<T>(string bundleName, string assetName) where T : UnityEngine.Object
    {
        targets.Clear();
        T asset = null;
#if UNITY_EDITOR
        asset = EditorAssetsLoadManager.LoadAsset<T>(bundleName, assetName);
#else
        AssetBundle assetBundle = LoadAssetBundle(bundleName);
        if (null == assetBundle) {
            asset = assetBundle.LoadAsset<T>(assetName);
        }
#endif
        return asset;
    }

    public void LoadScene(string bundleName, string sceneName, bool additive, Action<AsyncOperation> OnLoadCallback, bool keep = false)
    {
#if UNITY_EDITOR
        if (!EditorAssetsLoadManager.SimulatorAssetsLoad) {
            StartCoroutine(EditorAssetsLoadManager.LoadSceneCoroutine(bundleName, sceneName, additive, OnLoadCallback));
            return;
        }
#endif
        StartCoroutine(LoadSceneCoroutine(bundleName, sceneName, additive, OnLoadCallback, keep));
    }

    /// <summary>
    /// 场景加载协程
    /// </summary>
    /// <param name="bundleName">场景的AssetBundle名字</param>
    /// <param name="sceneName">场景名字，不带后缀</param>
    /// <param name="additive">是否附加场景</param>
    /// <param name="OnLoadCallback">完成回掉</param>
    /// <param name="keep">场景资源常驻</param>
    /// <returns></returns>
    private IEnumerator LoadSceneCoroutine(string bundleName, string sceneName, bool additive, Action<AsyncOperation> OnLoadCallback, bool keep = false)
    {
        List<string> needBundles = new List<string>();
        targets.Clear();
        CheckAssetBundlelDependencies(bundleName, needBundles, keep);
        yield return LoadAssetBundleAsync(needBundles, null);

        AssetBundle ab = getBundleFromCache(bundleName);
        string scenePath = null;
        if (null != ab && ab.isStreamedSceneAssetBundle) {
            string[] scenePaths = ab.GetAllScenePaths();
            string prefix = sceneName + ".unity";
            for(int i=0; i<scenePaths.Length; i++) {
                if(scenePaths[i].EndsWith(prefix)) {
                    scenePath = Path.GetFileNameWithoutExtension(scenePaths[i]);
                }
            }
        }
        if (!string.IsNullOrEmpty(scenePath)) {
            AsyncOperation asy = SceneManager.LoadSceneAsync(scenePath, additive ? LoadSceneMode.Additive : LoadSceneMode.Single);
            OnLoadCallback?.Invoke(asy);
            do {
                yield return asy;
                OnLoadCallback?.Invoke(asy);
            } while (!asy.isDone);
        } else {
            Debug.LogErrorFormat("Scene not found: {0}, {1}...", bundleName, sceneName);
        }
        
    }


    private void CheckAssetBundlelDependencies(string bundleName, List<string> needBundles, bool bPermanent = false)
    {
        if (bPermanent && !permanentBundles.ContainsKey(bundleName)) {
            permanentBundles.Add(bundleName, null);
        }

        targets.Add(bundleName);
        string[] dependencies = manifest.GetAllDependencies(bundleName);
        for (int i = 0; i < dependencies.Length; i++) {
            string tmpName = dependencies[i];
            if (bPermanent && !permanentBundles.ContainsKey(tmpName)) {
                permanentBundles.Add(tmpName, null);
            }
            if (null == getBundleFromCache(tmpName) && !targets.Contains(tmpName)) {
                CheckAssetBundlelDependencies(tmpName, needBundles, bPermanent);
            }
        }
        needBundles.Add(bundleName);
    }

    private IEnumerator LoadAssetBundleAsync(List<string> needBundles, Action OnLoadCallback)
    {
        int remain = needBundles.Count;
        for (int i = 0; i < needBundles.Count; i++) {
            string bundleName = needBundles[i];

            if (null != getBundleFromCache(bundleName)) {
                remain--;
                continue;
            }

            string pathName = string.Format("{0}/{1}", PathDefine.PersistentBundlePath, bundleName);
            // load assetbundle
            AssetBundle ab = AssetBundle.LoadFromFile(pathName);
            if (null != ab) {
                addBundleToCache(bundleName, ab);
                remain--;
            } else {
                Debug.LogErrorFormat("AssetBundle not found: {0}...", bundleName);
            }

            if (0 == remain) {
                break;
            }
        }
        OnLoadCallback?.Invoke();
        yield return null;
    }

}
